#!/usr/bin/env bash
# shellcheck disable=SC1090,SC2046,SC2206,SC2207

set -euo pipefail

export LANG=C.UTF-8

global_basedir=${global_basedir:-$(realpath "$(dirname "$0")")}

# By default the new genesis creation is disabled
: "${WB_MODULAR_GENESIS:=0}"

# For genesis creating, create-testnet-data is the default CLI command; set to 0 to fall back to create-staked
: "${WB_CREATE_TESTNET_DATA:=1}"

# By default, enable the new database storage backend for `locli`
: "${WB_LOCLI_DB:=1}"

. "$global_basedir"/lib.sh
. "$global_basedir"/env.sh
. "$global_basedir"/chaindb.sh
. "$global_basedir"/manifest.sh
. "$global_basedir"/nomad.sh
. "$global_basedir"/publish.sh
. "$global_basedir"/run.sh
. "$global_basedir"/scenario.sh

. "$global_basedir"/analyse/analyse.sh
. "$global_basedir"/profile/profile.sh
. "$global_basedir"/genesis/genesis.sh
. "$global_basedir"/topology/topology.sh

. "$global_basedir"/evaluate/evaluate.sh

. "$global_basedir"/backend/backend.sh
. "$global_basedir"/backend/nomad.sh
. "$global_basedir"/backend/nomad/cloud.sh
. "$global_basedir"/backend/nomad/exec.sh
. "$global_basedir"/backend/supervisor.sh

usage_main() {
    set +x
    test $# -lt 1 || msg "Unknown op: $1"
     __usage "OP" "Top-level OPs" <<EOF
    $(red profile)      ($(red p))      Cluster profile ops.   Default subop is $(yellow "${profile_default_op:?"profile_default_op not set"}")
    $(red run)          ($(red r))      Managing cluster runs. Default subop is $(yellow "${run_default_op:?"run_default_op not set"}")
    $(red analyse)      ($(red a))      Analyse cluster runs.  Default subop is $(yellow "${analyse_default_op:?"analyse_default_op not set"}")
    $(red publish)      ($(red u))      Bench data publish.    Default subop is $(yellow "${publish_default_op:?"publish_default_op not set"}")

    $(blue Secondary top-level OPs):

    $(red chaindb)      ($(red c))      ChainDB
    $(red genesis)      ($(red g))      Genesis
    $(red nomad)        ($(red n))      Nomad and Vault
    $(red topology)     ($(red t))      Topology generation
    $(red evaluate)     ($(red e))      Evaluate workbench module-based configuration

    $(white wb) $(blue options):

    $(helpopt --trace / --debug)        Enable workbench tracing (for error reports) ('set -x')
    $(helpopt --enable-modular-genesis) Enable NixOS modules based genesis configuration
    $(helpopt --help)                   This short help
    $(helpopt --help-full / --aliases)  Extended help (extra ops & subop aliases)
EOF
}

usage_extra() {
	cat >&2 <<EOF
  $(blue Extra top-level OPs):

    $(red start $(green \[FLAGS..]))       Start a run of profile chosen at 'nix-shell' entry
    $(red finish)                Finish an active cluster run
    $(red scenario)     ($(red s))      Run scenario control
    $(red backend)      ($(red b))      Backend calls
    $(red call) $(green ARGS..)       Call internal functions with arguments

  $(blue List of top-level) $(yellow subop) $(blue aliases, per) $(red op).  $(blue For detailed help:) $(red OP --help):

  ..$(red profile):$(color yellow)
          profile-names              names        lsp
          profile-json                            pj
          profile-describe           pdesc        pd
          profile-node-specs         node-specs   specs

  ..$(red run):$(color yellow)
          list-runs                  runs         lsr
          list-remote                remote       lsrr
          list-verbose               verb         lsrv
          list-verbose-remote        rverb        lsrv
          list-sets                  sets         lss
          list-sets-remote           rsets        lssr
          set-add                    add          sa
          run-or-set                              ros
          list-pattern               lspat        lsrp
          fix-legacy-run-structure   fix-legacy   flrs
          fetch-run                  fetch        fr
          fetch-analysis             fa

  ..$(red analyse):$(color yellow)
          compare                                 cmp
          variance                                var
          standard                   full         std
          block-propagation          blockprop    bp
          performance                             perf
          performance-host           perf-host
          trace-frequencies          trace-freq   freq tf
          chain-rejecta-reasons      chain-rejecta   rejecta
  ..$(red publish):$(color yellow)
          local                                   loc
$(color white)
EOF
}

usage_start()
{
    cat <<EOF
$(red USAGE:)
   $(helpcmd start \[FLAGS..])

   $(blue Flags):

      $(helpopt --batch-name NAME)               Override the batch name (default: $(green "${batchName:-default}"))

      $(helpopt --iterations \| --times \| --iter \| -n ITERATIONS)
                                      Run this many iterations of the profile.

      $(helpopt --profile-data DIR)              Path to generic profile data.
      $(helpopt --backend-data DIR)              Path to backend-specific data.

      $(helpopt --idle)                          Use the $(blue idle) scenario.
      $(helpopt --scenario \| -s SCENARIO)        Use the specified scenario.
                                        See: $(helptopcmd wb scenario --help)

      $(helpopt --no-analysis)                   Do not run analysis at the run's end.
      $(helpopt --analysis-can-fail)             Do not fail on failed analysis.
      $(helpopt --filters F,F,F,F)               Comma-separated list of filters, instead
                                        of profile-specified defaults.

      $(helpopt --supervisor \| --backend-supervisor)
                                      Use the $(blue supervisor) backend.

      $(helpopt --trace \| --debug)               Trace the start-cluster script
      $(helpopt --trace-wb \| --trace-workbench)  Trace the workbench script
      $(helpopt --verbose)                       Print more of what's going
      $(helpopt --help)                          This help message
EOF
}

start() {
    local batch_name=
    local profile_data=
    local backend_data=
    local node_source=.
    local node_rev=
    local ident=${ID:-}
    local cabal_mode=
    local prebuild_done=
    local manifest="{}"
    local iterations=1
    local no_retry_failed_runs=t
    local no_analysis=
    local analysis_can_fail=

    local run_args=()
    local run_allocate_args=()
    local run_start_args=()
    local analyse_args=(--dump-machviews)
    while test $# -gt 0
    do case "$1" in
        --batch-name )                   batch_name=$2;   shift;;
        --ident )                        ident=$2; shift;;
        --iterations | --times | --iter | -n ) iterations=$2; no_retry_failed_runs=; shift;;
        --cache-dir )                    setenvjqstr 'cacheDir' "$2"; shift;;
        --base-port )                    setenvjq    'basePort' "$2"; shift;;

        --backend-data )                 backend_data=$2; shift;;

        --cabal-mode | --cabal )         cabal_mode=t;;
        --node-source )                  node_source=$2; shift;;
        --node-rev )                     node_rev=$2; shift;;

        ## Allocate
        --genesis-cache-entry )          run_allocate_args+=($1 $2); shift;;
        --profile-data )                 run_allocate_args+=($1 $2); profile_data=$2; shift;;
        -- )                             shift ## Bulk-pass:
                                         run_allocate_args+=("$@")
                                         break;;

        ## Start
        --idle )                         run_start_args+=($1);;
        --scenario | -s )                run_start_args+=($1 $2); shift;;

        ## Analyse
        --no-analysis )                  no_analysis=true;;
        --analysis-can-fail | -af )      analysis_can_fail=t;;
        --dump-logobjects )              analyse_args+=($1);;
        --filters )                      analyse_args+=($1 $2); shift;;
        --locli-file )                   export WB_LOCLI_DB=0;;         # use the legacy file backend of `locli`

        ## Aux
        --verbose | -v )                 export verbose=t;;
        --trace | --debug )              set -x;;
        --trace-wb | --trace-workbench ) export WB_EXTRA_FLAGS=--trace;;
        --create-testnet-data )          export WB_CREATE_TESTNET_DATA=1;;

        --help )                         usage_start
                                         exit 1;;
        * ) fatal "while parsing remaining '$(blue wb start)$(red \' args):  $(white "$@")";;
       esac; shift; done

    if test -n "$cabal_mode"
    then . $(dirname "$0")/lib-cabal.sh ${WB_PROFILING:+--profiling-${WB_PROFILING}}
    fi

    backend validate
    backend assert-stopped
    backend setenv-defaults "$backend_data"

    profile_name=$(jq '.name' -r "$profile_data/profile.json")
    analysis_type=$(jq '.analysis.type' -r "$profile_data/profile.json")

    local top_i runs=()
    for ((top_i=0; top_i<iterations; top_i++))
    do newline
       if test -n "$cabal_mode" -a -z "$prebuild_done"
       then
            (
              # This step is resource intensive so we use a lockfile to avoid
              # running it in parallel to a benchmark.
              acquire_lock
              workbench-prebuild-executables
            )
            prebuild_done=t
       fi

       # manifest generation needs the build plan that is created in the previous step
       progress "manifest" "component versions:"
       local manifest
       manifest=$(manifest collect-from-checkout  \
                      "$node_source"              \
                      "$node_rev"                 \
                      "${WB_MANIFEST_PACKAGES[@]}"
                 )
       manifest render "$manifest"

       progress "top-level" "profile $(with_color 'yellow' $profile_name), iteration $(with_color 'white' $((top_i+1))) of $(with_color 'yellow' $iterations), identified as:  $(if test -n "$ident"; then echo $(white $ident); else echo $(red UNIDENTIFIED); fi)"

       local allocate_args=(
           $batch_name
           $profile_name
           $WB_BACKEND
            --manifest "$manifest"
            ${run_allocate_args[@]}
       )
       run "${run_args[@]}" allocate "${allocate_args[@]}"
       local run
       run=$(run current-tag)
       runs+=($run)

       current_run_path=$(run current-path)
       mkdir -p "$current_run_path"

       run "${run_args[@]}" start "${run_start_args[@]}" "$run"

       if test -n "$no_analysis" -o "$analysis_type" = null; then continue; fi

       progress "top-level | analysis" "analysis type $(with_color yellow "$analysis_type") on $(with_color white "$run")"

       local runspec
       if test -n "$ident"; then
         runspec="$ident":$run
       else
         runspec=$run
       fi

       if analyse "${analyse_args[@]}" "$analysis_type" "$runspec"; then
         progress "run | analysis" "done for $(white "$runspec")"
       else
         if test -n "$analysis_can_fail" -a -z "$no_retry_failed_runs"; then
           progress "run | analysis" "log processing failed, but --analysis-can-fail prevents failure:  $(with_color red "$runspec")"
           iterations=$((iterations + 1))
         else
           fail "analysis failed:  $run"
           false
         fi
       fi
    done

    if test -z "$no_analysis" -a "$analysis_type" != null -a "$iterations" -gt 1
    then analyse variance "${runs[@]}"
    fi
}

finish()
{
    while test $# -gt 0
    do case "$1" in
        --trace | --debug )              set -x;;
        --trace-wb | --trace-workbench ) export WB_EXTRA_FLAGS=--trace;;
        * ) break;; esac; shift; done

    run stop $(run current-tag)
}

while test $# -gt 0
do case "$1" in
       --nomad      | --backend-nomad )      export WB_BACKEND=nomad      ;;
       --supervisor | --backend-supervisor ) export WB_BACKEND=supervisor ;;
       --enable-modular-genesis )            export WB_MODULAR_GENESIS=1  ;;

       --cls )                          echo -en "\ec">&2;;
       --verbose | -v )                 export verbose=t;;
       --trace | --debug )              set -x;;
       --trace-wb | --trace-workbench ) export WB_EXTRA_FLAGS=--trace;;
       --help )
           usage_main; exit 1;;
       --help-full | --help-all | --help-extra | --help-aliases | --aliases )
           usage_main; usage_extra; exit 1;;
       * ) break;; esac; shift; done

main() {
    # if $1 is not set ...
    if [ ! -v 1 ]; then
      usage_main
      exit 1
    fi
    local op=$1
    shift

    case "$op" in
        ## Public, primary:
        #
        start            )        start               "$@";;
        finish           )        finish              "$@";;

        ## Public, secondary:
        profile      | profiles | prof | ps | p )
                                  profile             "$@";;
        run          | r )        run                 "$@";;
        analyse      | a )        analyse             "$@";;
        publish      | u )        publish             "$@";;
        evaluate     | e )        evaluate            "$@";;

        ## Public, internals-y:
        chaindb      | c )        chaindb             "$@";;
        genesis      | g )        genesis             "$@";;
        manifest     | m )        manifest            "$@";;
        nomad        | n )        wb_nomad            "$@";;
        topology     | topo | t ) topology            "$@";;

        ## 'profile' aliases:
          profile-names            | names      | lsp           \
        | profile-json                          | pj            \
        | profile-describe         | pdesc      | pd            \
        | profile-node-specs       | node-specs | specs         \
          )                       profile "$op"        "$@";;

        ## 'run' aliases:
          list-runs                | runs       | lsr           \
        | list-remote              | remote     | lsrr          \
        | list-verbose             | verb       | lsrv          \
        | list-verbose-remote      | rverb      | lsrrv         \
        | list-sets                | sets       | lss           \
        | list-sets-remote         | rsets      | lssr          \
        | set-add                  | add        | sa            \
        | run-or-set                            | ros           \
        | list-pattern             | lspat      | lsrp          \
        | fix-legacy-run-structure | fix-legacy | flrs          \
        | fetch-run                | fetch      | fr            \
        | fetch-analysis           | fa                         \
        | trim                                                  \
        | package                  | pack                       \
        | set-identifier                        | setid         \
          )                       run $op             "$@";;

        ## 'analyse' aliases:
          compare                               | cmp           \
        | variance                              | var           \
        | standard                 | full       | std           \
        | block-propagation        | blockprop  | bp            \
        | performance                           | perf          \
        | performance-host         | perf-host                  \
        | trace-frequencies        | trace-freq | freq | tf     \
        | chain-rejecta-reasons    | chain-rejecta | rejecta    \
        | render-comparison-pdf    | render-pdf | pdf           \
          )                       analyse "$op"        "$@";;

        ## Internals:
        #
        scenario     | s )        scenario            "$@";;
        backend      | b )        backend             "$@";;
        call )                                        "$@";;
        path )                    echo "$global_basedir"  ;;

        ## Bail for help:
        * )
          usage_main "$op"
          exit 1
          ;;
    esac
}

main "$@"
